package de.rs.espresso.algorithms;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import de.rs.espresso.covers.DefaultCover;
import de.rs.espresso.cubes.CubeFactory;
import de.rs.espresso.interfaces.IAlgorithm;
import de.rs.espresso.interfaces.ICover;
import de.rs.espresso.interfaces.ICube;
import de.rs.espresso.interfaces.IGlobalInformation;
import de.rs.espresso.tools.Statistics;

public class Complement implements IAlgorithm {

	enum ComplementOf {
		ONSET, OFFSET
	}

	private ComplementOf complOf;
	private IGlobalInformation info;
	
	private int output = 0;

	public Complement(IGlobalInformation info) {
		//FIXME: does not work with multiple output
		this.complOf = ComplementOf.ONSET;
		this.info = info;
	}

	public Complement(IGlobalInformation info, ComplementOf complOf) {
		this.complOf = complOf;
		this.info = info;
	}

	public ICover compute(ICover onSet, ICover offSet, ICover dcSet) {
		
		long measurementId = Statistics.startMeasurement("COMPLEMENT");

		ICover toComplementing = null;

		switch (complOf) {
		case ONSET:
			assert (onSet != null);
			toComplementing = onSet.clone();
			break;
		case OFFSET:
			assert (offSet != null);
			toComplementing = offSet.clone();
			break;
		default:
			assert (false);
		}

		if (dcSet != null) {
			toComplementing.addAll(dcSet.clone());
		}
		
		ICover result = complementAlgorithm(toComplementing);

		Statistics.stopMeasurement(measurementId);
		
		return result;

	}

	private ICover complementAlgorithm(ICover set) {

		List<ICover> covers = set.getSingleOutputCovers(); 
		
		ICover result = new DefaultCover(info);
		
		if(covers.size()==1&&covers.get(0).isVoid()) {
			result.add(CubeFactory.getDontCareCube(info));
			return result;
		}

		for (ICover c : covers) {
			if (!c.isVoid()) {
				ICover compl = comp1(c);
				result.addAll(compl);
				output++;
			}
			else {
				//TODO ausgang setzen!!!
				result.add(CubeFactory.getDontCareCube(info));
				return result;
			}
		}

		return result;
	}

	private ICover comp1(ICover cover) {

		for (ICube cube : cover) {
			if (cube.isTautology())
				return new DefaultCover(info);
		}

		if (cover.isUnate()) {
			return unateComplement(cover);
		}

		int literalPosition = cover.getMostBinateLiteral();

		ICube dcCube = CubeFactory.getDontCareCube(info);

		ICube splittingCubeLeft = dcCube.clone();
		splittingCubeLeft.setLiteral(literalPosition, ICube.DIRECT);

		ICube splittingCubeRight = dcCube;
		splittingCubeRight.setLiteral(literalPosition, ICube.NEGATED);
		
		ICover coFactorLeft = cover.getCofactor(splittingCubeLeft);
		ICover coFactorRight = cover.getCofactor(splittingCubeRight);		

		return mergeWithContainment(comp1(coFactorLeft)	,
				comp1(coFactorRight), literalPosition);

	}

	private ICover mergeWithContainment(ICover cover1, ICover cover2,
			int splittingPosition) {

		/*
		 * Merge with identity algorithm
		 */

		ICover result = new DefaultCover(info);
		ICover toCheck1 = cover1.clone();
		ICover toCheck2 = cover2.clone();

		result.addAll(cover1);
		result.retainAll(cover2);

		toCheck1.removeAll(cover2);
		toCheck2.removeAll(cover1);

		/*
		 * Merge with containment algorithm
		 */

		List<ICube> toDeleteFromToCheck1 = new ArrayList<ICube>();
		List<ICube> toDeleteFromToCheck2 = new ArrayList<ICube>();

		for (ICube c1 : toCheck1) {
			for (ICube c2 : toCheck2) {
				if (c1.covers(c2)) {
					result.add(c2);
					toDeleteFromToCheck2.add(c2);
				} else if (c2.covers(c1)) {
					result.add(c1);
					toDeleteFromToCheck1.add(c1);
				}
			}
		}

		toCheck1.removeAll(toDeleteFromToCheck1);
		toCheck2.removeAll(toDeleteFromToCheck2);

		for (ICube cube : toCheck1) {
			cube.setLiteral(splittingPosition, ICube.DIRECT);
			result.add(cube);
		}
		for (ICube cube : toCheck2) {
			cube.setLiteral(splittingPosition, ICube.NEGATED);
			result.add(cube);
		}

		return result;
	}

	private ICover unateComplement(ICover cover) { // functionality of
													// PERS_UNATE_COMPLEMENT,
													// personality matrix left
													// out..

		/*
		 * check for special cases:
		 */

		/*
		 * case1: row of 2's
		 */

		for (ICube cube : cover) {
			if (cube.isTautology()) {
				return new DefaultCover(info);
			}
		}

		/*
		 * case2: Cover is empty
		 */

		if (cover.isVoid()) {
			ICover result = new DefaultCover(info);
			ICube c = CubeFactory.getDontCareCube(info);
			for(int i = 0; i<info.getNumberOfOutputs(); i++) {
				if(i!=output)
					c.setOutput(i, ICube.OUTPUT_NOT_PRESENT);
			}
			result.add(c);
			
			return result;
		}

		/*
		 * case3: Cover contains a single cube, return complemented Cover with
		 * deMorgan rules
		 */
		cover.removeVoidCubes();

		if (cover.size() == 1)
			return cover.get(0).getComplement();

		// if no special case applies, select splitting variable and branch!

		int literalPosition = cover.unateComplementSelect();

		ICube dcCube = CubeFactory.getDontCareCube(info);

		ICube splittingCubeLeft = dcCube.clone();
		splittingCubeLeft.setLiteral(literalPosition, ICube.DIRECT);

		ICube splittingCubeRight = dcCube;
		splittingCubeRight.setLiteral(literalPosition, ICube.NEGATED);

		ICover posCofactor = cover.getCofactor(splittingCubeLeft);
		

		ICover negCofactor = cover.getCofactor(splittingCubeRight);

		//TODO: Special Merger needed or not?!
		return mergeWithContainment(unateComplement(posCofactor),
				unateComplement(negCofactor), literalPosition);
	}

}
